package com.easyUploader.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.common.exception.CustomException;
import com.common.mybaits.CustomPage;
import com.common.util.StringUtil;
import com.easyUploader.entity.dao.Bucket;
import com.easyUploader.entity.dao.FileStorage;
import com.easyUploader.entity.dto.FileInfoDto;
import com.easyUploader.entity.vo.FileChunkVo;
import com.easyUploader.entity.dto.FileUpLoadResultDto;
import com.easyUploader.entity.vo.FileUploadCheckVo;
import com.easyUploader.mapper.FileChunkMapper;
import com.easyUploader.mapper.FileStorageMapper;
import com.easyUploader.property.LocalFileProperty;
import com.easyUploader.service.IBucketService;
import com.easyUploader.service.IFileStorageService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easyUploader.threadHolder.FileInfoThreadHolder;
import com.easyUploader.threadHolder.UserThreadHolder;
import com.easyUploader.util.FileUtil;
import com.easyUploader.util.PathUtil;
import com.easyUploader.util.ZipUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.List;

import static com.easyUploader.entity.factory.FileChunkFactory.FILE_CHUNK_CONVERTER;

/**
 * <p>
 * 文件存储 服务实现类
 * </p>
 *
 * @author 大忽悠
 * @since 2022年10月29日
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class FileStorageServiceImpl extends ServiceImpl<FileStorageMapper, FileStorage> implements IFileStorageService {
    private final FileChunkMapper fileChunkMapper;
    private final IBucketService iBucketService;
    private final LocalFileProperty localFileProperty;

    @Override
    public void saveLocalStorage(String resultFileName, FileChunkVo fileChunkVo) {
        String extensionName = FileUtil.getExtensionName(fileChunkVo.getFileName());
        FileStorage fileStorage = FileStorage.builder()
                .realName(fileChunkVo.getFileName())
                .renamedName(fileChunkVo.getFileName())
                .identifier(fileChunkVo.getFileIdentifier())
                .createBy(UserThreadHolder.get().getId())
                .updateBy(UserThreadHolder.get().getId())
                .size(String.valueOf(fileChunkVo.getFile().getSize()))
                .suffix(extensionName)
                .type(FileUtil.getFileType(extensionName))
                .fileSavePath(resultFileName)
                .fileVisitPath(localFileProperty.getFileRelativePath(fileChunkVo))
                .bucketId(FileInfoThreadHolder.getBucketId())
                .build();
        save(fileStorage);
    }


    /**
     * 当前文件是否已经被完整上传
     *
     * @param fileUploadCheckVo
     * @return
     */
    @Override
    public FileUpLoadResultDto checkFileUpLoad(FileUploadCheckVo fileUploadCheckVo) {
        fileUploadCheckVo.safeCheck(iBucketService);
        FileStorage fileStorage = doCheckFileUpLoad(fileUploadCheckVo.getFileMd5(),
                localFileProperty.getFileSavePath(fileUploadCheckVo), fileUploadCheckVo.getFileName(),
                localFileProperty.getFileRelativePath(fileUploadCheckVo), fileUploadCheckVo.getBucketName());
        //文件已经上传
        if (fileStorage != null) {
            String visitUrl = localFileProperty.getFileVisitUrl(fileStorage);
            log.info("当前分片访问的URL为: {}", visitUrl);
            return FileUpLoadResultDto.builder().uploaded(Boolean.TRUE).visitUrl(visitUrl).build();
        }
        //返回当前文件已经上传的分片集合
        return FileUpLoadResultDto.builder()
                .uploaded(Boolean.FALSE)
                .readChunkList(fileChunkMapper.getUploadedTrunkSeqList(fileUploadCheckVo.getFileMd5())).build();
    }

    @Override
    public FileStorage doCheckFileUpLoad(String fileMd5, String fileSavePath, String fileName, String fileVisitPath, String bucketName) {
        //如果采用硬链接形式共享文件数据块,那么可能文件存储表中会存在多个md5值相同的记录
        List<FileStorage> fileStorageList = list(new QueryWrapper<FileStorage>().eq("identifier", fileMd5));
        if (ObjectUtils.isEmpty(fileStorageList)) {
            return null;
        }
        FileStorage copyFileStorage = fileStorageList.get(0);
        FileStorage returnFileStorage = null;
        //只要两个文件实例准备存放的路径不一致,那么就采用硬链接实现文件共享
        //如果存在两个文件存放路径一样,说明此文件可以采用秒传策略
        boolean canQuickUpload = Boolean.FALSE;
        for (FileStorage fileStorage : fileStorageList) {
            if (fileStorage.getFileSavePath().equals(fileSavePath)) {
                canQuickUpload = Boolean.TRUE;
                returnFileStorage = fileStorage;
                break;
            }
        }
        //不能秒传,说明可以采用硬链接实现共享
        if (!canQuickUpload) {
            if (!FileUtil.createHardLink(fileSavePath, copyFileStorage.getFileSavePath())) {
                //检查硬链接创建失败,是否因为对应的文件已经存在--要确保md5值和当前要上传文件的md5值一致才行,否则删除已经存在的文件
                //TODO: 对于大文件而言,计算md5的时间不可忍受,不如直接重新上传来的方便
                //如果文件系统不支持硬链接,或者硬链接建立过程中出现错误,那么此次文件共享失败,用户需要上传完整文件
                log.info("当前操作系统不支持硬链接");
                return null;
            }
            log.info("当前文件采用硬链接技术进行上传,文件相关信息如下: 文件名--{},文件保存路径--{}", fileName, fileSavePath);
            //硬链接创建成功,那么需要更新数据库文件记录
            returnFileStorage = copyFileStorage.copy(fileSavePath, fileName, fileVisitPath);
            Bucket bucket = iBucketService.getBucketByName(bucketName);
            returnFileStorage.setBucketId(bucket.getId());
            save(returnFileStorage);
        }
        return returnFileStorage;
    }


    @Override
    public void downLoadByBucketAndFileName(String bucket, String path, HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        FileStorage fileStorage = getFileStorage(iBucketService, bucket, path);
        File file = null;
        String downLoadFileName = null;
        Boolean deleteZipFile = Boolean.FALSE;
        try {
            //不存在对应的文件,或者当前name指代的是目录--暂不支持下载目录
            if (ObjectUtils.isEmpty(fileStorage)) {
                //如果发现对应的name指代的是某个目录,那么把目录打成压缩包后进行下载
                file = Bucket.zipDirIfNeeded(bucket, path, localFileProperty);
                //也不是目录--那么忽略
                if (file == null) {
                    return;
                }
                downLoadFileName = file.getName();
                deleteZipFile = Boolean.TRUE;
            } else {
                file = fileStorage.getFile();
                downLoadFileName = fileStorage.getRealName();
            }
            FileUtil.downloadFile(request, response, file, downLoadFileName, false);
        } finally {
            //删除压缩文件
            if (deleteZipFile) {
                file.delete();
            }
        }
    }

    private FileStorage getFileStorage(IBucketService iBucketService, String bucket, String path) {
        Bucket bucketOne = iBucketService.getBucketByName(bucket);
        return getOne(new QueryWrapper<FileStorage>().eq("bucket_id", bucketOne.getId())
                .eq("file_save_path", localFileProperty.getFilePathByOnlineUser(bucket, path)));
    }

    @Override
    public Boolean deleteByBucketAndFileName(String bucket, String path, HttpServletRequest request, HttpServletResponse response) {
        FileStorage fileStorage = getFileStorage(iBucketService, bucket, path);
        if (ObjectUtils.isEmpty(fileStorage)) {
            return Boolean.TRUE;
        }
        //删除文件,再删除数据库记录
        FileUtil.deleteFile(fileStorage.getFileSavePath());
        removeById(fileStorage);
        return Boolean.TRUE;
    }

    @Override
    public void visitByBucketAndFileName(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        String leftUrl = localFileProperty.stripVisitUrlWithContextPathAndVisitPrefix(request.getRequestURI());
        String bucket = getBucketFromRequest(leftUrl);
        FileStorage fileStorage = getFileStorage(iBucketService, bucket, getPathFromRequest(bucket, leftUrl));
        showFile(fileStorage, request, response);
    }

    private void showFile(FileStorage fileStorage, HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        //不存在对应的文件
        if (ObjectUtils.isEmpty(fileStorage)) {
            throw new IllegalArgumentException("不存在对应的文件(目前版本暂不支持直接访问某个目录)");
        }
        //展示文件
        FileUtil.showFile(request, response, fileStorage.getFile(), fileStorage.getRealName(), fileStorage.getSuffix());
    }

    @Override
    public void visitShareByFileName(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        //default+相对路径
        String leftUrl = localFileProperty.stripShareVisitUrlWithContextPathAndVisitPrefix(URLDecoder.decode(request.getRequestURI()));
        String filePath = localFileProperty.getShareFileOrDirPathByRelativePath(leftUrl);
        log.info("当前访问共享文件的路径为: {}",filePath);
        File file = new File(filePath);
        if(!file.exists()||file.isDirectory()){
            throw new IllegalArgumentException("资源不存在或者请求的资源是目录(暂不支持访问目录)");
        }
        FileStorage fileStorage = FileStorage.builder().fileSavePath(filePath).realName(file.getName()).suffix(FileUtil.getSuffix(file)).build();
        //不存在对应的文件
        showFile(fileStorage, request, response);
    }

    @Override
    public void downLoadShareFile(String filePath, HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        File file = new File(localFileProperty.getShareFileOrDirPathByRelativePath(filePath));
        //如果文件不存在,直接返回
        if(!file.exists()){
            throw new CustomException("所要下载的文件或目录资源不存在","所要下载的文件或目录资源不存在");
        }
        String downLoadFileName = file.getName();
        Boolean deleteZipFile = Boolean.FALSE;
        try {
            if (file.isDirectory()) {
                //把目录打成压缩包后进行下载
                file = ZipUtil.zipIfNeeded(file.getAbsolutePath(), localFileProperty.getZipTempDir());
                downLoadFileName = file.getName();
                deleteZipFile = Boolean.TRUE;
            }
            FileUtil.downloadFile(request, response, file, downLoadFileName, false);
        } finally {
            //删除压缩文件
            if (deleteZipFile) {
                file.delete();
            }
        }
    }

    /**
     * bucketName+filePath
     */
    private String getBucketFromRequest(String leftUrl) {
        if (StringUtil.isNullOrEmpty(leftUrl)) {
            return "";
        }
        return URLDecoder.decode(leftUrl.split("/")[0]);
    }

    /**
     * 剥去contextPath+固定的访问前缀+桶名
     */
    private String getPathFromRequest(String bucketName, String leftUrl) {
        return PathUtil.modifyPathByOs(URLDecoder.decode(StringUtil.stripStrFromBegin(bucketName, leftUrl)));
    }
}
